<!DOCTYPE HTML>
<html lang="en" dir="ltr">
	<head prefix="og: http://ogp.me/ns# article: http://ogp.me/ns/article#">
		<meta charset="utf-8">

		<title>Fun with the new bpfdoor (2023) &mdash; unfinished.bike</title>
		
		<link rel="stylesheet" type="text/css" href="https://cdn.writeas.net/css/write.8fac221726b68760e79b7.css" />
		
		<link rel="shortcut icon" href="https://cdn.writeas.net/favicon.ico" />
		
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		
		<link rel="canonical" href="https://unfinished.bike/fun-with-the-new-bpfdoor-2023" />
		<link rel="alternate" type="application/rss+xml" title="unfinished.bike &raquo; Feed" href="https://unfinished.bike/feed/" />
		<meta name="generator" content="Write.as">
		<meta name="title" content="Fun with the new bpfdoor (2023) &mdash; unfinished.bike">
		<meta name="description" content="I was recently provided a sample of the recently announced stealthier variant of bpfdoor, malware targeting Linux that is almost certainl...">
		
		<meta name="twitter:label1" value="Views">
		<meta name="twitter:data1" value="806">
		<link rel="author" href="http://unfinished.bike/" />
		<meta name="author" content="unfinished.bike" />
		<meta itemprop="description" content="I was recently provided a sample of the recently announced stealthier variant of bpfdoor, malware targeting Linux that is almost certainl...">
		<meta itemprop="datePublished" content="2023-05-14" />
		<meta name="twitter:card" content="summary_large_image">
		<meta name="twitter:description" content="I was recently provided a sample of the recently announced stealthier variant of bpfdoor, malware targeting Linux that is almost certainl...">
		<meta name="twitter:title" content="Fun with the new bpfdoor (2023) &mdash; unfinished.bike">
		<meta name="twitter:image" content="https://i.snap.as/XIxA7RGD.jpg">
		<meta property="og:title" content="Fun with the new bpfdoor (2023)" />
		<meta property="og:description" content="I was recently provided a sample of the recently announced stealthier variant of bpfdoor, malware targeting Linux that is almost certainl..." />
		<meta property="og:site_name" content="unfinished.bike" />
		<meta property="og:type" content="article" />
		<meta property="og:url" content="https://unfinished.bike/fun-with-the-new-bpfdoor-2023" />
		<meta property="og:updated_time" content="2023-05-14T02:12:18Z" />
		<meta property="og:image" content="https://i.snap.as/XIxA7RGD.jpg" />
		<meta property="article:published_time" content="2023-05-14T02:12:18Z">
		
        
		<style type="text/css">#wrapper, #collection article, #post article {
    max-width: 50em !important;
}</style>
		

	</head>
	<body id="post">
		
		<div id="overlay"></div>

		<header>
		<h1 dir="ltr" id="blog-title"><a rel="author" href="/" class="h-card p-author">unfinished.bike</a></h1>
			<nav>
				
				
				
				
			</nav>
		</header>

		

		<article id="post-body" class="norm h-entry "><h2 id="title" class="p-name dated">Fun with the new bpfdoor (2023)</h2><time class="dt-published" datetime="2023-05-14T02:12:18Z" pubdate itemprop="datePublished" content="2023-05-14 02:12:18 &#43;0000 UTC">May 14, 2023</time><div class="e-content"><p>I was recently provided a sample of the recently announced <a href="https://www.deepinstinct.com/blog/bpfdoor-malware-evolves-stealthy-sniffing-backdoor-ups-its-game">stealthier variant of bpfdoor</a>, malware targeting Linux that is almost certainly a state-funded Chinese threat actor (<a href="https://malpedia.caad.fkie.fraunhofer.de/actor/red_menshen">Red Menshen</a>). The sample analyzed was   <a href="https://www.virustotal.com/gui/file/afa8a32ec29a31f152ba20a30eb483520fe50f2dce6c9aa9135d88f7c9c511d7">a8a32ec29a31f152ba20a30eb483520fe50f2dce6c9aa9135d88f7c9c511d7</a>, detectable by 11 of 62 detectors on VirusTotal.</p>

<p>I was particularly curious what the bpfdoor surface area looked like, and if it was easy it was to detect using existing open-source tools and common Linux command-line utilities.</p>

<p><img src="https://i.snap.as/XIxA7RGD.jpg" alt=""/></p>

<p>To experiment,  I used my favorite VM manager on macOS or Linux for this analysis: <a href="https://github.com/lima-vm/lima">Lima</a>, with the default Ubuntu 22.10 image.</p>

<h2 id="running-bpfdoor-as-a-regular-user" id="running-bpfdoor-as-a-regular-user">Running bpfdoor as a regular user</h2>

<p>I first ran bpfdoor as an unprivileged user to see what system calls would be executed:</p>

<pre><code class="language-shell">strace -o /tmp/st.user -f ./x.bin
</code></pre>

<p>I&#39;ve removed the less interesting lines of output, but the program does astonishingly little:</p>

<pre><code class="language-log">2655  execve(&#34;./x.bin&#34;, [&#34;./x.bin&#34;], 0x7fff9dad6ff8 /* 23 vars */) = 0
2655  openat(AT_FDCWD, &#34;/lib/x86_64-linux-gnu/libc.so.6&#34;, O_RDONLY|O_CLOEXEC) = 3
2655  openat(AT_FDCWD, &#34;/var/run/initd.lock&#34;, O_RDWR|O_CREAT, 0666) = -1 EACCES (Permission denied)
2655  flock(-1, LOCK_EX|LOCK_NB)        = -1 EBADF (Bad file descriptor)
2655  clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7ff8d1b39a10) = 2656
2655  +++ exited with 0 +++
2656  close(0)                          = 0
2656  close(1)                          = 0
2656  close(2)                          = 0
2656  setsid()                          = 2656
2656  getrandom(&#34;\xa4\xd5\x9d\x71\xb3\xe0\x98\xe1&#34;, 8, GRND_NONBLOCK) = 8
2656  socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)) = -1 EPERM (Operation not permitted)
2656  exit_group(0)                     = ?
2656  +++ exited with 0 +++
</code></pre>

<p>The only noteworthy things here are:</p>
<ul><li>It tries to create /var/run/initd.lock but fails because it requires root</li>
<li>It tries to set up a raw socket to listen to all protocols but fails because it requires root.</li>
<li>It forks into the background via <code>clone()</code> and <code>setsid()</code>.</li></ul>

<p>It&#39;s not unusual to see a bug with the flock() call to fd=-1 because openat() returned an error rather than a file handle.</p>

<h2 id="running-as-root" id="running-as-root">Running as root</h2>

<pre><code class="language-log">2669  openat(AT_FDCWD, &#34;/var/run/initd.lock&#34;, O_RDWR|O_CREAT, 0666) = 3
2669  flock(3, LOCK_EX|LOCK_NB)         = 0
2669  clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fb6d948ba10) = 3319
2669  exit_group(0 &lt;unfinished ...&gt;
3319  close(0 &lt;unfinished ...&gt;
2669  +++ exited with 0 +++
3319  close(1)                          = 0
3319  close(2)                          = 0
3319  setsid()                          = 3319
3319  getrandom(&#34;\x6c\x07\x1c\x75\x6b\xae\xfe\xdf&#34;, 8, GRND_NONBLOCK) = 8
3319  socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)) = 0
3319  setsockopt(0, SOL_SOCKET, SO_ATTACH_FILTER, {len=30, filter=0x7ffd2270fa90}, 16) = 0
3319  recvfrom(0, &#34;RUU\341\314\22RU\300\250\5\2\10\0E\0\0Lp\220\0\0@\6~\272\300\250\5\2\300\250&#34;..., 65536, 0, NULL, NULL) = 90
3319  recvfrom(0, &#34;RUU\341\314\22RU\300\250\5\2\10\0E\0\0(p\221\0\0@\6~\335\300\250\5\2\300\250&#34;..., 65536, 0, NULL, NULL) = 54
3319  recvfrom(0, &#34;RUU\341\314\22RU\300\250\5\2\10\0E\0\0Lp\222\0\0@\6~\270\300\250\5\2\300\250&#34;..., 65536, 0, NULL, NULL) = 90
3319  recvfrom(0, &#34;RUU\341\314\22RU\300\250\5\2\10\0E\0\0(p\223\0\0@\6~\333\300\250\5\2\300\250&#34;..., 65536, 0, NULL, NULL) = 54
</code></pre>

<p>First, it opens a lock, which works this time:</p>

<pre><code class="language-shell">-rw-r--r-- 1 root root 0 May 13 12:45 /run/initd.lock
</code></pre>

<p>As mentioned in the <a href="https://www.deepinstinct.com/blog/bpfdoor-malware-evolves-stealthy-sniffing-backdoor-ups-its-game">bpfdoor analysis by deep instinct</a>, we can see that it sets a BPF filter via <code>setsockopt()</code>, and loops waiting for the magic byte sequence: <code>\x44\x30\xCD\x9F\x5E\x14\x27\x66</code>.</p>

<p>One thing I find fascinating is how simple the initialization is: the <a href="https://www.elastic.co/security-labs/a-peek-behind-the-bpfdoor">previous iteration of bpfdoor</a> did so much more in the name of “stealth”:</p>
<ul><li>copies itself to <code>/dev/shm</code></li>
<li>renaming itself in the process table via <code>prctl</code></li>
<li>deletes itself from disk</li>
<li>timestomping</li></ul>

<p>Red Menshen must have noticed that every method for achieving stealth is also a reliable detection method. So, the new bpfdoor keeps it simple by not trying to be stealthy. In fact, this binary does so little that it&#39;s suspicious. In 2023, most advanced evasion methods are not worth it on Linux: it is good enough to hide in plain sight.</p>

<h2 id="detection" id="detection">Detection</h2>

<p>Using the <code>make detect</code> rule from
<a href="https://github.com/chainguard/osquery-detection-kit">osquery-detection-kit</a>, I examined which existing rules would alert on the presence of the latest bpfdoor. 3 of them did:</p>
<ul><li><p><a href="https://github.com/chainguard-dev/osquery-defense-kit/blob/main/detection/execution/unexpected-raw-socket.sql">unexpected raw socket</a>: unexpected packet sniffers, just like this one! Near-zero false-positive rate.</p></li>

<li><p><a href="https://github.com/chainguard-dev/osquery-defense-kit/blob/main/detection/execution/recently-created-executables-linux.sql">recently created executables</a>: programs executed within 45 seconds of when it likely landed on disk, based on ctime and btime. This catch-all has found every malware it&#39;s encountered, but it requires a comprehensive exception list.</p></li>

<li><p><a href="https://github.com/chainguard-dev/osquery-defense-kit/blob/main/detection/evasion/unexpected-var-run-linux.sql">unexpected /var/run file</a>: Inspired by reading the bpfdoor technical analysis, it&#39;s good to see this fired when faced with the real thing.</p></li></ul>

<p>That said, I think we can do better. Let&#39;s see what the malware looks like from /proc.</p>

<h2 id="exploring-bpfdoor-using-proc" id="exploring-bpfdoor-using-proc">Exploring bpfdoor using /proc</h2>

<p>To get an idea of what I can use for further detecting bpfdoor, I wanted to see how it was seen via /proc. First, what libraries does it link against? Based on the report, I&#39;m not expecting anything other than libc:</p>

<pre><code class="language-shell">% sudo cat /proc/3319/maps

00400000-00448000 r-xp 00000000 fc:01 3210                               /tmp/x.bin
00648000-00649000 r--p 00048000 fc:01 3210                               /tmp/x.bin
00649000-0064a000 rw-p 00049000 fc:01 3210                               /tmp/x.bin
0064a000-0066a000 rw-p 00000000 00:00 0 
00c36000-00c57000 rw-p 00000000 00:00 0                                  [heap]
7fb6d9200000-7fb6d9222000 r--p 00000000 fc:01 3648                       /usr/lib/x86_64-linux-gnu/libc.so.6
7fb6d9222000-7fb6d939b000 r-xp 00022000 fc:01 3648                       /usr/lib/x86_64-linux-gnu/libc.so.6
7fb6d939b000-7fb6d93f2000 r--p 0019b000 fc:01 3648                       /usr/lib/x86_64-linux-gnu/libc.so.6
7fb6d93f2000-7fb6d93f6000 r--p 001f1000 fc:01 3648                       /usr/lib/x86_64-linux-gnu/libc.so.6
7fb6d93f6000-7fb6d93f8000 rw-p 001f5000 fc:01 3648                       /usr/lib/x86_64-linux-gnu/libc.so.6
7fb6d93f8000-7fb6d9405000 rw-p 00000000 00:00 0 
7fb6d948b000-7fb6d948e000 rw-p 00000000 00:00 0 
7fb6d9495000-7fb6d9497000 rw-p 00000000 00:00 0 
7fb6d9497000-7fb6d9498000 r--p 00000000 fc:01 3645                       /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7fb6d9498000-7fb6d94c1000 r-xp 00001000 fc:01 3645                       /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7fb6d94c1000-7fb6d94cb000 r--p 0002a000 fc:01 3645                       /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7fb6d94cb000-7fb6d94cd000 r--p 00034000 fc:01 3645                       /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7fb6d94cd000-7fb6d94cf000 rw-p 00036000 fc:01 3645                       /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7ffd226f0000-7ffd22711000 rw-p 00000000 00:00 0                          [stack]
7ffd22720000-7ffd22724000 r--p 00000000 00:00 0                          [vvar]
7ffd22724000-7ffd22726000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  [vsyscall]
</code></pre>

<p>What about open file handles?</p>

<pre><code class="language-shell">% sudo lsof -p 3319

COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
x.bin   3319 root  cwd    DIR   0,52      200    9 /tmp/lima/osquery-defense-kit/out
x.bin   3319 root  rtd    DIR  252,1     4096    2 /
x.bin   3319 root  txt    REG  252,1   302576 3210 /tmp/x.bin
x.bin   3319 root  mem    REG  252,1  2072888 3648 /usr/lib/x86_64-linux-gnu/libc.so.6
x.bin   3319 root  mem    REG  252,1   228720 3645 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
x.bin   3319 root    0u  pack  33049      0t0  ALL type=SOCK_RAW
x.bin   3319 root    3u   REG   0,25        0 1322 /run/initd.lock
</code></pre>

<p>lsof is handy, but to see the raw socket from /proc, we need to do a little bit more digging:</p>

<pre><code class="language-shell"># cat /proc/net/packet

sk               RefCnt Type Proto  Iface R Rmem   User   Inode
ffff92d346ba6800 3      3    88cc   2     1 0      100    19458 
ffff92d34631d800 3      3    0003   0     1 241920 0      33089 
</code></pre>

<p>The <code>Inode</code> field is misleading, but you can use it to find the associated process ID via:</p>

<pre><code class="language-shell">$ sudo find /proc -type l -lname &#34;socket:\[33089\]&#34; 2&gt;/dev/null

/proc/3319/task/3319/fd/0
/proc/3319/fd/0
</code></pre>

<p>Alternatively, you can use this to see all filehandles for the process ID:</p>

<pre><code class="language-shell">$ ls -la /proc/3319/fd

total 0
dr-x------ 2 root root  0 May 13 13:03 .
dr-xr-xr-x 9 root root  0 May 13 13:03 ..
lrwx------ 1 root root 64 May 13 13:03 0 -&gt; &#39;socket:[33089]&#39;
lrwx------ 1 root root 64 May 13 13:03 3 -&gt; /run/initd.lock
</code></pre>

<p>Once you have a process ID, you can resolve the path to the program:</p>

<pre><code class="language-shell">sudo ls -lad /proc/3319/exe

lrwxrwxrwx 1 root root 0 May 14 00:48 /proc/3319/exe -&gt; /tmp/x.bin
</code></pre>

<h2 id="exploring-bpfdoor-using-strings" id="exploring-bpfdoor-using-strings">Exploring bpfdoor using strings</h2>

<p>Running <code>strings &lt;path&gt;</code> reveals some interesting messages:</p>

<pre><code class="language-log">[-] Execute command failed
/var/run/initd.lock
</code></pre>

<p>libtom/libtomcrypt has been bundled in, so we see lines such as:</p>

<pre><code class="language-log">LTC_ARGCHK &#39;%s&#39; failure on line %d of file %s
X.509v%i certificate
  Issued by: [%s]%s (%s)
  Issued to: [%s]%s (%s, %s)
  Subject: %s
  Validity: %s - %s
  OCSP: %s
  Serial number:
...
LibTomCrypt 1.17 (Tom St Denis, tomstdenis@gmail.com)
LibTomCrypt is public domain software.
Built on Oct  4 2022 at 16:09:32
</code></pre>

<p>That last string is important: this iteration of bpfdoor could have been wandering around Cyberspace since October 2022 (7 months ago) without detection. It also appears that the bad guys used Red Hat Enterprise Linux 7.0 (nearly 10 years old!) to build the binary:</p>

<pre><code class="language-log">GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-44)
</code></pre>

<h2 id="new-detection-possibilities" id="new-detection-possibilities">New detection possibilities</h2>

<p>After looking at /proc, a couple of new detection ideas came up:</p>
<ul><li>Programs with /var/run lock files open</li>
<li>Root processes with a socket and no shared libraries</li>
<li>World-readable lock files in /var/run</li>
<li>Minimalist socket users with few open files</li>
<li>Processes where fd 0 is a non-UNIX socket</li></ul>

<p>There are certainly more possibilities depending on how this backdoor is launched: for example, based on cwd or cgroup. I have not yet seen information published on how this backdoor is actually executed.</p>

<p>I implemented each of these detection ideas: once for osquery to use in production, and once in shell just for fun. The osquery queries have been tested across Ubuntu, Fedora, Arch Linux, and NixOS, and the shell scripts have only been tested on Ubuntu.</p>

<h3 id="programs-with-run-lock-files-left-open" id="programs-with-run-lock-files-left-open">Programs with /run lock files left open</h3>

<p>It&#39;s unusual for a program to have an open file in /var/run, but I suspect this may eventually find a false positive. Here&#39;s an osquery and a shell script to find these:</p>

<pre><code class="language-sql">SELECT p.* FROM processes p JOIN process_open_files pof ON p.pid = pof.pid AND pof.path LIKE &#34;/run/%.lock&#34;;
</code></pre>

<pre><code class="language-shell">sudo find /proc -lname &#34;/run/*.lock&#34; 2&gt;/dev/null
</code></pre>

<h3 id="root-processes-with-a-socket-and-no-shared-libraries" id="root-processes-with-a-socket-and-no-shared-libraries">Root processes with a socket and no shared libraries</h3>

<p>Most programs that use a socket are either fully static, or import a library like OpenSSL. bpfdoor isn&#39;t either. Here is another osquery and shell pair:</p>

<pre><code class="language-sql">SELECT p.*,
    COUNT(DISTINCT pmm.path) AS pmm_count
FROM processes p
    JOIN process_open_sockets pos ON p.pid = pos.pid
    LEFT JOIN process_memory_map pmm ON p.pid = pmm.pid
    AND pmm.path LIKE &#34;%.so.%&#34;
    -- Yes, this is a weird performance optimization
WHERE p.pid IN (
        SELECT pid
        FROM processes
        WHERE p.euid = 0
            AND p.path NOT IN (
                &#39;/usr/bin/containerd&#39;,
                &#39;/usr/bin/fusermount3&#39;,
                &#39;/usr/sbin/acpid&#39;,
                &#39;/usr/sbin/mcelog&#39;,
                &#39;/usr/bin/docker-proxy&#39;
            )
    )
GROUP BY pos.pid -- libc.so, ld-linux
HAVING pmm_count = 2;
</code></pre>

<pre><code class="language-shell">cd /proc || exit

for pid in *; do
    [[ ! -f ${pid}/exe || ${pid} =~ &#34;self&#34; ]] &amp;&amp; continue

    euid=$(grep Uid /proc/${pid}/status | awk &#39;{ print $2 }&#39;)
    [[ &#34;${euid}&#34; != 0 ]] &amp;&amp; continue

    sockets=$(sudo find /proc/${pid}/fd -lname &#34;socket:*&#34; | wc -l)
    [[ &#34;${sockets}&#34; == 0 ]] &amp;&amp; continue

    libs=$(sudo find /proc/${pid}/map_files/ -type l -lname &#34;*.so.*&#34; -exec readlink {} \; | sort -u | wc -l)
    [[ &#34;${libs}&#34; != 2 ]] &amp;&amp; continue

    path=$(readlink /proc/$pid/exe)
    name=$(cat /proc/$pid/comm)
    echo &#34;euid=0 process with sockets and no libs: ${name} [${pid}] at ${path}&#34;
done
</code></pre>

<h3 id="world-readable-lock-files-in-var-run" id="world-readable-lock-files-in-var-run">World readable lock files in /var/run</h3>

<p>Typically lock files are readable only by the root user. Malware often uses very relaxed file permissions.</p>

<pre><code class="language-sql">SELECT * FROM file WHERE path LIKE &#34;/tmp/%.lock&#34; AND mode = &#34;0644&#34;;
</code></pre>

<pre><code class="language-shell">find /run/*.lock -perm 644
</code></pre>

<h3 id="minimalist-socket-users-with-few-open-files" id="minimalist-socket-users-with-few-open-files">Minimalist socket users with few open files</h3>

<p>This creative query reveals minimalist programs that behave like a backdoor might:</p>
<ul><li>have 0-1 open files</li>
<li>have 1-2 sockets open</li></ul>

<p>It&#39;s an uncommon situation, but it is bound to have false positives in software that is designed in a way that each process has a specific role:</p>

<pre><code class="language-sql">SELECT p.pid,
    p.path,
    p.name,
    p.start_time,
    GROUP_CONCAT(DISTINCT pos.protocol) AS protocols,
    pof.path AS pof_path,
    COUNT(DISTINCT pos.fd) AS scount,
    COUNT(DISTINCT pof.path) AS fcount,
    GROUP_CONCAT(DISTINCT pof.path) AS open_files,
    p.cgroup_path
FROM processes p
    JOIN process_open_sockets pos ON p.pid = pos.pid
    AND pos.protocol &gt; 0
    LEFT JOIN process_open_files pof ON p.pid = pof.pid
WHERE p.start_time &lt; (strftime(&#39;%s&#39;, &#39;now&#39;) -60)
AND p.path NOT IN (
    &#39;/bin/registry&#39;,
    &#39;/usr/bin/docker-proxy&#39;,
    &#39;/usr/sbin/chronyd&#39;,
    &#39;/usr/sbin/cups-browsed&#39;,
    &#39;/usr/sbin/cupsd&#39;,
    &#39;/usr/sbin/sshd&#39;
)
AND p.path NOT LIKE &#39;/nix/store/%-openssh-%/bin/sshd&#39;
GROUP BY p.pid
HAVING scount &lt;= 2
    AND fcount &lt;= 1;
</code></pre>

<pre><code class="language-shell">cd /proc || exit

for pid in *; do
    [[ ! -f ${pid}/exe || ${pid} =~ &#34;self&#34; ]] &amp;&amp; continue

    fds=$(find /proc/${pid}/fd -lname &#34;/*&#34; | wc -l)
    [[ &#34;${fds}&#34; == 0 ]] &amp;&amp; continue
    [[ &#34;${fds}&#34; -gt 1 ]] &amp;&amp; continue

    # WARNING: ss -xp will print two fds on the same line if connected. Use grep -o instead of -c
    #ss -xp | grep -v &#34;^u_&#34; | grep -o pid=${pid},&#34;

    all_sockets=$(find /proc/${pid}/fd -lname &#34;socket:*&#34; | wc -l)
    [[ &#34;${all_sockets}&#34; -gt 2 ]] &amp;&amp; continue

    # this isn&#39;t exactly what we want - ss doesn&#39;t show TYPE=sock of protocol=UNIX :(
    unix_sockets=$(ss -ap | grep &#34;^u_&#34; | grep -o &#34;pid=${pid},&#34; | wc -l)
    sockets=$(($all_sockets - $unix_sockets))

    [[ &#34;${sockets}&#34; == 0 ]] &amp;&amp; continue
    [[ &#34;${sockets}&#34; -gt 2 ]] &amp;&amp; continue

    path=$(readlink /proc/$pid/exe)
    [[ &#34;${path}&#34; == &#34;/usr/sbin/sshd&#34; ]] &amp;&amp; continue

    name=$(cat /proc/$pid/comm)
    echo &#34;minimalist socket user (${sockets} sockets and ${fds} files): ${name} [${pid}] at ${path}&#34;
done

</code></pre>

<h3 id="fd0-is-a-socket" id="fd0-is-a-socket">fd0 is a socket</h3>

<p>I&#39;ve saved my favorite for last. File descriptor 0 is usually stdin, but in bpfdoors case, it is actually the socket it uses to listen to traffic on. I&#39;ve never seen this behavior before outside of bpfdoor:</p>

<pre><code class="language-sql">SELECT * FROM process_open_sockets WHERE fd=0 AND family != 1;
</code></pre>

<pre><code class="language-shell">cd /proc || exit

for pid in *; do
    [[ ! -f ${pid}/exe || ${pid} =~ &#34;self&#34; ]] &amp;&amp; continue

    ino=$(readlink /proc/$pid/fd/0 | grep -o &#39;socket:.*&#39; | cut -d&#34;[&#34; -f2 | cut -d&#34;]&#34; -f1)
    grep -q &#34; ${ino}&#34; /proc/$pid/net/unix &amp;&amp; continue

    path=$(readlink /proc/$pid/exe)
    name=$(cat /proc/$pid/comm)
    echo &#34;fd0 is a socket: ${name} [${pid}] at ${path}&#34;
done
</code></pre>

<h2 id="final-thoughts" id="final-thoughts">Final Thoughts</h2>

<p>Ultimately, I was happy to see that this variant was detectable using osquery-defense-kit, and even happier that I could add additional rules to find future similar malware. Two philosophical viewpoints are critical to success in detection:</p>
<ul><li>Knowing what is considered normal in your environment</li>
<li>Evasion is a means of detection</li></ul>

<p>If you are interested in open-source queries that can find bpfdoor and other unusual programs, check out:</p>
<ul><li><a href="https://github.com/chainguard-dev/osquery-defense-kit/">https://github.com/chainguard-dev/osquery-defense-kit/</a></li>
<li><a href="https://github.com/tstromberg/sunlight">https://github.com/tstromberg/sunlight</a></li></ul>

<p>Thanks to <a href="https://cyberplace.social/@GossiTheDog">Kevin Beaumont</a> for providing the bpfdoor sample for analysis.</p>
</div></article>

		

		
		<footer dir="ltr"><hr><nav><p style="font-size: 0.9em">published with <a class="home pubd" href="https://write.as/">write.as</a></p></nav></footer>
		

		<noscript><p><img src="https://analytics.write.as/piwik.php?idsite=16" style="border:0;" alt="" /></p></noscript>
	</body>
	
	
		
		
	
	
<script>
  
  addEventListener('DOMContentLoaded', function () {
    var hlbaseUri = "https:\/\/cdn.writeas.net/js/";
    var lb = document.querySelectorAll("code[class^='language-']");

    
    var aliasmap = {
      "elisp"      : "lisp",
      "emacs-lisp" : "lisp",
      "c"          : "cpp",
      "cc"         : "cpp",
      "h"          : "cpp",
      "c++"        : "cpp",
      "h++"        : "cpp",
      "hpp"        : "cpp",
      "hh"         : "cpp",
      "hxx"        : "cpp",
      "cxx"        : "cpp",
      "sh"         : "bash",
      "js"         : "javascript",
      "jsx"        : "javascript",
      "html"       : "xml"
    };

    
    function highlight(nodes) {
      for (i=0; i < nodes.length; i++) {
        hljs.highlightBlock(nodes[i]);
      }
    }

    
    function loadLanguages(uris, callback) {
      uris.forEach(function(uri) {
        var sc = document.createElement('script');
        sc.src = uri;
        sc.async = false; 
        
        if (uris.indexOf(uri) == uris.length-1) {
          
          
          
          sc.onload = callback;
          sc.onerror = callback;
        }
        document.head.appendChild(sc);
      });
    }

    
    if (lb.length > 0) {
      
      var st = document.createElement('link');
      st.rel = "stylesheet";
      st.href = "https:\/\/cdn.writeas.net/css/lib/atom-one-light.min.css";
      document.head.appendChild(st);

      
      var jss = [hlbaseUri + "highlight.min.js"];
      
      for (i=0; i < lb.length; i++) {
        lang = lb[i].className.replace('language-','').toLowerCase();
        
        if (aliasmap[lang]) lang = aliasmap[lang];
        lurl = hlbaseUri + "highlightjs/" + lang + ".min.js";
        if (!jss.includes(lurl)) {
          jss.push(lurl);
        }
      }
      
      loadLanguages(jss, () => {highlight(lb)});
    }
  });
</script>

	<script src="https://cdn.writeas.net/js/localdate.js" integrity="sha384-2h0jAAXW06POyeBB2kpmJH+tWBF2mCWnv4DucLFRZXs+D8NX/MjGV7C/aCC2Ywki" crossorigin="anonymous"></script>
	<script type="text/javascript">
	
		var http = new XMLHttpRequest();
		var url = "/api/collections/thomrstrom/posts/n1053gnxswswmali/stat";
		http.open("POST", url, true);
		http.setRequestHeader("Content-type", "application/json");
		http.send();
	

var pinning = false;
function unpinPost(e, postID) {
	e.preventDefault();
	if (pinning) {
		return;
	}
	pinning = true;

	var $header = document.getElementsByTagName('header')[0];
	var callback = function() {
		
		var $pinnedNavLink = $header.getElementsByTagName('nav')[0].querySelector('.pinned.selected');
		$pinnedNavLink.style.display = 'none';
		try { _paq.push(['trackEvent', 'Post', 'unpin', 'post']); } catch(e) {}
	};

	var $pinBtn = $header.getElementsByClassName('unpin')[0];
	$pinBtn.innerHTML = '...';

	var http = new XMLHttpRequest();
	var url = "/api/collections/thomrstrom/unpin";
	var params = [ { "id": postID } ];
	http.open("POST", url, true);
	http.setRequestHeader("Content-type", "application/json");
	http.onreadystatechange = function() {
		if (http.readyState == 4) {
			pinning = false;
			if (http.status == 200) {
				callback();
				$pinBtn.style.display = 'none';
				$pinBtn.innerHTML = 'Pin';
			} else if (http.status == 409) {
				$pinBtn.innerHTML = 'Unpin';
			} else {
				$pinBtn.innerHTML = 'Unpin';
				alert("Failed to unpin." + (http.status>=500?" Please try again.":""));
			}
		}
	}
	http.send(JSON.stringify(params));
};

var $form = document.getElementById('emailsub');
if ($form != null) {
	$form.onsubmit = function() {
		var $sub = document.getElementById('subscribe-btn');
		$sub.disabled = true;
		$sub.value = 'Subscribing...';
	}
}

	

	
	try { 
	  var _paq = _paq || [];
	  _paq.push(['trackPageView']);
	  _paq.push(['enableLinkTracking']);
	  _paq.push(['enableHeartBeatTimer']);
	  (function() {
		var u="https://analytics.write.as/";
		_paq.push(['setTrackerUrl', u+'piwik.php']);
		_paq.push(['setSiteId', 16]);
		var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
		g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
	  })();
	} catch (e) {   }
	
	try { 
	  WebFontConfig = {
		custom: { families: [ 'Lora:400,700:latin', 'Open+Sans:400,700:latin' ], urls: [ 'https:\/\/cdn.writeas.net/css/fonts.4ce1882.css' ] }
	  };
	  (function() {
		var wf = document.createElement('script');
		wf.src = 'https:\/\/cdn.writeas.net/js/webfont.js';
		wf.type = 'text/javascript';
		wf.async = 'true';
		var s = document.getElementsByTagName('script')[0];
		s.parentNode.insertBefore(wf, s);
	  })();
	} catch (e) {   }
	</script>

    
</html>